package com.joyoung.sweep.utils.blue;

import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter.LeScanCallback;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.text.TextUtils;

import java.util.Arrays;
import java.util.UUID;

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class Ble {

    private static final String TAG = "Ble";

    public static final int BLE_DISCOVERY_PERIOD = 20 * 1000;
    private static final String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";

    private Context mContext;
    private BluetoothAdapter mAdapter;
    private LeScanCallback mLeScanCallback;
    private BluetoothGatt mBluetoothGatt;
    private BluetoothGattCallback mBluetoothGattCallback;
    private BluetoothGattCharacteristic mNotifyGattCharacteristic;
    private BluetoothGattCharacteristic mReadWriteGattCharacteristic;
    private BleCallback mTransferCallback;
    private Handler mHandler = new Handler();
    private Runnable mCancelScanRunnable;
    private String mMac;
    private boolean mStartScan;

    private String serviceUuid;
    private String readWriteCharacteristicUuid;
    private String notifyCharacteristicUuid;

    /**
     * @param mTransferCallback the mTransferCallback to set
     */
    public void setCallback(BleCallback mTransferCallback) {
        this.mTransferCallback = mTransferCallback;
    }

    /**
     * @return the serviceUuid
     */
    public String getServiceUuid() {
        return serviceUuid;
    }

    /**
     * @param serviceUuid the serviceUuid to set
     */
    public void setServiceUuid(String serviceUuid) {
        this.serviceUuid = serviceUuid;
    }

    /**
     * @return the readWriteCharacteristicUuid
     */
    public String getReadWriteCharacteristicUuid() {
        return readWriteCharacteristicUuid;
    }

    /**
     * @param readWriteCharacteristicUuid the readWriteCharacteristicUuid to set
     */
    public void setReadWriteCharacteristicUuid(String readWriteCharacteristicUuid) {
        this.readWriteCharacteristicUuid = readWriteCharacteristicUuid;
    }

    /**
     * @return the notifyCharacteristicUuid
     */
    public String getNotifyCharacteristicUuid() {
        return notifyCharacteristicUuid;
    }

    /**
     * @param notifyCharacteristicUuid the notifyCharacteristicUuid to set
     */
    public void setNotifyCharacteristicUuid(String notifyCharacteristicUuid) {
        this.notifyCharacteristicUuid = notifyCharacteristicUuid;
    }

    public static Ble getInstance(Context context) {

        if (BleTransferInner.instance.mContext == null) {
            BleTransferInner.instance.init(context);
        }
        return BleTransferInner.instance;
    }

    private static class BleTransferInner {
        private static final Ble instance = new Ble();
    }

    private void init(Context context) {
        this.mContext = context;
        BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
        mAdapter = manager.getAdapter();
        mLeScanCallback = new LeScanCallback() {

            @Override
            public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
                LogUtil.v(TAG, "------>onLeScan: " + device);
                String deviceName = device.getName();
                String deviceAddress = device.getAddress();
                LogUtil.e(TAG, "===>>>扫描到设备： " + deviceName + " / " + deviceAddress + " / " + rssi);
                if (TextUtils.equals(deviceName, BleLinker.getInstance(mContext).getBleName())) {
                    mMac = deviceAddress;
                    stopScanDevice();
                    //找到蓝牙设备
                    if (mTransferCallback != null) {
                        mTransferCallback.onDeviceFind(device, rssi, scanRecord);
                    }
                }
            }
        };
        mBluetoothGattCallback = new BluetoothGattCallback() {

            @Override
            public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
                super.onConnectionStateChange(gatt, status, newState);
                LogUtil.i(TAG, String.format("onConnectionStateChange: status-%s newState-%s", status, newState));

                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    LogUtil.i(TAG, "onConnectionStateChange: STATE_CONNECTED, discover services...");

                    if (mBluetoothGatt != null) {
                        mBluetoothGatt.discoverServices();
                    }
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    LogUtil.i(TAG, "onConnectionStateChange: STATE_DISCONNECTED !!!");

                    if (mTransferCallback != null) {
                        mTransferCallback.onConnectionChanged(BleCallback.STATE_DISCONNECTED, status);
                    }
                }
            }

            @Override
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                super.onServicesDiscovered(gatt, status);
                LogUtil.v(TAG, "onServicesDiscovered: status-" + status);

                int state = -1;
                if (status == BluetoothGatt.GATT_SUCCESS) {

                    for (BluetoothGattService service : gatt.getServices()) {

                        LogUtil.d(TAG, "onServicesDiscovered:" + service.getUuid());

                        if (service.getUuid().equals(UUID.fromString(serviceUuid))) {

                            if (!TextUtils.isEmpty(notifyCharacteristicUuid)) {

                                mNotifyGattCharacteristic = service.getCharacteristic(UUID.fromString(notifyCharacteristicUuid));
                            }

                            if (!TextUtils.isEmpty(readWriteCharacteristicUuid)) {

                                mReadWriteGattCharacteristic = service.getCharacteristic(UUID.fromString(readWriteCharacteristicUuid));
                            }

                            state = BleCallback.STATE_CONNECTED;
                            break;
                        }
                    }

                    if (state == BleCallback.STATE_CONNECTED && mTransferCallback != null) {
                        mTransferCallback.onConnectionChanged(state, 0);
                    } else {
                        LogUtil.w(TAG, "Not find service " + serviceUuid);
                        disconnect();
                    }

                } else {
                    LogUtil.w(TAG, "onServicesDiscovered status: " + status);
                    mBluetoothGatt.disconnect();
                }
            }

            @Override
            public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
                super.onCharacteristicChanged(gatt, characteristic);

                LogUtil.i(TAG, "onCharacteristicChanged: address-" + gatt.getDevice().getAddress() + " uuid-" + characteristic.getUuid());
                if (mTransferCallback != null) {
                    LogUtil.w(TAG, "onCharacteristicChanged mTransferCallback is :" + mTransferCallback);
                    mTransferCallback.onDataNotified(characteristic.getValue());
                } else {
                    LogUtil.e(TAG, "收到数据，无法返回!!!!!! :");
                }
            }

            @Override
            public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                super.onCharacteristicWrite(gatt, characteristic, status);
                LogUtil.i(TAG, "onCharacteristicWrite: address-" + gatt.getDevice().getAddress() + " uuid-" + characteristic.getUuid() + " status-" + status);
                if (mTransferCallback != null) {
                    LogUtil.e(TAG, "收到数据：onCharacteristicWrite mTransferCallback ： " + mTransferCallback);
                    mTransferCallback.onDataWritten(characteristic.getValue(), status == BluetoothGatt.GATT_SUCCESS);
                }
            }

            @Override
            public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                super.onCharacteristicRead(gatt, characteristic, status);

                LogUtil.i(TAG, "onCharacteristicRead: address-" + gatt.getDevice().getAddress() + " characteristic-" + Arrays.toString(characteristic.getValue()) + " status-" + status);

                if (status == BluetoothGatt.GATT_SUCCESS && mTransferCallback != null) {
                    mTransferCallback.onDataRead(gatt, characteristic, characteristic.getValue());
                }
            }

            @Override
            public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
                super.onDescriptorWrite(gatt, descriptor, status);

                LogUtil.i(TAG, "onDescriptorWrite: address-" + gatt.getDevice().getAddress() + " descriptor-" + descriptor.getUuid() + " status-" + status);
                if (descriptor.getUuid().toString().equals(CLIENT_CHARACTERISTIC_CONFIG) && mTransferCallback != null) {

                    if (status == BluetoothGatt.GATT_SUCCESS) {

                        boolean enable = Arrays.equals(descriptor.getValue(), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                        if (enable) {
                            mTransferCallback.onNotifyChanged(true);
                        } else {
                            enable = Arrays.equals(descriptor.getValue(), BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                            if (enable) {
                                mTransferCallback.onNotifyChanged(false);
                            } else {
                                mTransferCallback.onNotifyChanged(null);
                            }
                        }
                    } else {
                        mTransferCallback.onNotifyChanged(null);
                    }
                }
            }

            @Override
            public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
                super.onDescriptorRead(gatt, descriptor, status);

                LogUtil.i(TAG, "onDescriptorRead: address-" + gatt.getDevice().getAddress() + " descriptor-" + descriptor.getUuid() + " status-" + status);
                if (descriptor.getUuid().toString().equals(CLIENT_CHARACTERISTIC_CONFIG) && mTransferCallback != null) {

                    if (status == BluetoothGatt.GATT_SUCCESS) {

                        boolean enable = Arrays.equals(descriptor.getValue(), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                        if (enable) {
                            mTransferCallback.onNotifyRead(true);
                        } else {
                            enable = Arrays.equals(descriptor.getValue(), BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                            if (enable) {
                                mTransferCallback.onNotifyRead(false);
                            } else {
                                mTransferCallback.onNotifyRead(null);
                            }
                        }
                    } else {
                        mTransferCallback.onNotifyRead(null);
                    }
                }
            }
        };
        mCancelScanRunnable = () -> {
            mAdapter.stopLeScan(mLeScanCallback);
            if (mTransferCallback != null) {
                mTransferCallback.onDeviceFind(false);
            }
            if (mStartScan) {

                mStartScan = false;
                if (mTransferCallback != null) {
                    mTransferCallback.onScanFinished();
                }
            }
        };
    }

    private Ble() {
    }

    public boolean hasAdapter() {
        return mAdapter != null;
    }

    public boolean isAdapterOn() {
        return mAdapter.isEnabled();
    }

    /**
     * Get the current state of the local Bluetooth adapter.
     * <p>Possible return values are
     * {@link BluetoothAdapter#STATE_OFF},
     * {@link BluetoothAdapter#STATE_TURNING_ON},
     * {@link BluetoothAdapter#STATE_ON},
     * {@link BluetoothAdapter#STATE_TURNING_OFF}.
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
     *
     * @return current state of Bluetooth adapter
     */
    public int getAdapterState() {
        return mAdapter.getState();
    }

    public static boolean hasAdapter(Context context) {
        return getAdapter(context) != null;
    }

    public static boolean isAdapterOn(Context context) {

        BluetoothAdapter adapter = getAdapter(context);
        return adapter != null && adapter.isEnabled();
    }

    /**
     * Starts a scan for a Bluetooth LE device with its MAC address.
     *
     * <p>Results of the scan are reported using the
     * {@link BleCallback#onDeviceFind(BluetoothDevice, int, byte[])} callback.
     *
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
     *
     * @param mac the MAC address of Bluetooth LE device to scan
     * @return true, if the scan was started successfully
     */
    public synchronized boolean scanDevice(final String mac) {
        LogUtil.i(TAG, "start ScanDevice, mac: " + mac);
        if (LinkerUtils.toMac(mac) == null) {
            throw new IllegalArgumentException("parameter mac is not invalid mac address: " + mac);
        }

        if (!mStartScan) {

            mStartScan = true;
            mMac = mac;
//			stopScanDevice();
            mHandler.postDelayed(mCancelScanRunnable, BLE_DISCOVERY_PERIOD);

            boolean success = mAdapter.startLeScan(mLeScanCallback);
            LogUtil.i(TAG, "scanDevice:" + mac + " success-" + success);
            return success;
        } else {
            return false;
        }
    }

    /**
     * Starts a scan for Bluetooth LE devices.
     *
     * <p>Results of the scan are reported using the
     * {@link BleCallback#onDeviceFind(BluetoothDevice, int, byte[])} callback.
     *
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
     *
     * @return true, if the scan was started successfully
     */
    public synchronized boolean scanDevice() {
        LogUtil.i(TAG, "start ScanDevice");
        if (!mStartScan) {
            mMac = null;
//            mScanDevices.clear();

            LogUtil.i(TAG, "scanDevice:");
//			stopScanDevice();
            mStartScan = true;
            mHandler.postDelayed(mCancelScanRunnable, BLE_DISCOVERY_PERIOD);
            return mAdapter.startLeScan(mLeScanCallback);
        } else {
            return false;
        }
    }

    /**
     * Stops an ongoing Bluetooth LE device scan.
     *
     * <p>reported using the
     * {@link BleCallback#onScanFinished()} callback when scan stoped.
     *
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
     */
    public synchronized void stopScanDevice() {
        LogUtil.i(TAG, "stopScanDevice");
        if (mStartScan) {

            mStartScan = false;
            mHandler.removeCallbacks(mCancelScanRunnable);
            mAdapter.stopLeScan(mLeScanCallback);
            if (mTransferCallback != null) {
                mTransferCallback.onScanFinished();
            }
        }
    }

    public synchronized boolean connectDevice(String mac) {

        mMac = mac;

        if (mMac == null) {
            throw new RuntimeException("mac is null");
        }

//        if (mBluetoothGatt != null) {
//
//            BluetoothDevice device = mAdapter.getRemoteDevice(mMac);
//            if (device == null) {
//                LogUtil.i(TAG, "Not find device with mac address: " + mMac);
//                onConnectDeviceFailed();
//                return false;
//            }
//
//            if (mBluetoothGatt != null && mBluetoothGatt.connect()) {
//
//                LogUtil.i(TAG, "a previous mBluetoothGatt is exist, connect it");
//                if (mTransferCallback != null) {
//                    mTransferCallback.onConnectionChanged(BleCallback.STATE_CONNECTING);
//                }
//                return true;
//            } else {
//                onConnectDeviceFailed();
//                return false;
//            }
//        }

        BluetoothDevice device = mAdapter.getRemoteDevice(mMac);
        if (device == null) {
            LogUtil.i(TAG, "Not find device with mac address: " + mMac);
            onConnectDeviceFailed();
            return false;
        }
        mBluetoothGatt = device.connectGatt(mContext, false, mBluetoothGattCallback);
        if (mBluetoothGatt != null && mTransferCallback != null) {
            mTransferCallback.onConnectionChanged(BleCallback.STATE_CONNECTING, 0);
        }

        return true;
    }

    public synchronized boolean connectDevice() {
        return connectDevice(mMac);
    }

    public synchronized void disconnect() {

        LogUtil.i(TAG, "disconnect");
        if (mBluetoothGatt != null) {
            LogUtil.i(TAG, "mBluetoothGatt.disconnect");
            mBluetoothGatt.disconnect();
        }
    }

    public synchronized void close() {

        LogUtil.i(TAG, "close");
        if (mBluetoothGatt != null) {
            mBluetoothGatt.disconnect();
            mBluetoothGatt.close();
            mBluetoothGatt = null;
            LogUtil.i(TAG, "mBluetoothGatt.close");
        }
    }

    /**
     * Write the data to characteristic with uuid({@link #readWriteCharacteristicUuid}).
     * A {@link BleCallback#onDataWritten(byte[], boolean)}  callback is triggered
     * to report the result of the write operation.
     *
     * @param data
     */
    public void write(final byte[] data) {

        LogUtil.i(TAG, "write: " + LinkerUtils.bytes2HexStringWithWhitespace(data));
        if (mReadWriteGattCharacteristic != null && mBluetoothGatt != null) {
            LogUtil.i(TAG, "setValue:" + mReadWriteGattCharacteristic.setValue(data));
//            mReadWriteGattCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
            mReadWriteGattCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
            boolean writeCharacteristicResult = mBluetoothGatt.writeCharacteristic(mReadWriteGattCharacteristic);
            LogUtil.i(TAG, "writeCharacteristic:" + writeCharacteristicResult);
        }
    }

    /**
     * Write the data on characteristic with uuid({@link #readWriteCharacteristicUuid}).
     * A {@link BleCallback#onDataRead(BluetoothGatt, BluetoothGattCharacteristic, byte[])} callback is triggered
     * to report the result of the read operation.
     */
    public void read() {
        LogUtil.i(TAG, "read");
        mBluetoothGatt.readCharacteristic(mReadWriteGattCharacteristic);
    }

    /**
     * Enable the notify. A {@link BleCallback#onNotifyChanged(Boolean)} callback is triggered
     * to report the result of the enable operation.
     *
     * @param enable
     */
    public void enableNotify(boolean enable) {
        LogUtil.d(TAG, String.format("Enable notify? %s", enable));
        if (mBluetoothGatt != null && mNotifyGattCharacteristic != null) {

            mBluetoothGatt.setCharacteristicNotification(mNotifyGattCharacteristic, enable);

            BluetoothGattDescriptor descriptor = mNotifyGattCharacteristic.getDescriptor(UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG));
            if (descriptor != null) {
                descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                mBluetoothGatt.writeDescriptor(descriptor);
            } else {
                LogUtil.w(TAG, "Config characteristic is null!");
            }
        }
    }

    /**
     * Read the notify enabled status, A {@link BleCallback#onNotifyRead(Boolean)} callback is triggered
     * to report the result of the read operation.
     */
    public void readNotifyEnabled() {

        LogUtil.i(TAG, "readNotifyEnabled:mNotifyGattCharacteristic-" + mNotifyGattCharacteristic);

        if (mBluetoothGatt != null && mNotifyGattCharacteristic != null) {

            BluetoothGattDescriptor descriptor = mNotifyGattCharacteristic.getDescriptor(UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG));
            if (descriptor != null) {
                mBluetoothGatt.readDescriptor(descriptor);
            }
        }
    }

    public BluetoothDevice getBluetoothDevice() {
        if (mAdapter != null) {
            return mAdapter.getRemoteDevice(mMac);
        }
        return null;
    }

    public static boolean isBleSupported(Context context) {
        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
    }

    /**
     * @return the mAdapter
     */
    public BluetoothAdapter getAdapter() {
        return mAdapter;
    }

    public static BluetoothManager getBluetoothManager(Context context) {
        return (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
    }

    /**
     * @return the mAdapter
     */
    public static BluetoothAdapter getAdapter(Context context) {
        return getBluetoothManager(context).getAdapter();
    }

    private void onConnectDeviceFailed() {

        if (mTransferCallback != null) {
            mTransferCallback.onConnectionChanged(BleCallback.STATE_DISCONNECTED, 0);
        }
        disconnect();
        close();
    }

    public static void requestEnableBluetoothAdapter(Context context) {
        context.startActivity(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE));
    }

    public static void registerBluetoothStateChangedListener(Context context, OnBluetoothStateChangedListener listener) {
        context.registerReceiver(listener, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
    }

    public static void unregisterBluetoothStateChangedListener(Context context, OnBluetoothStateChangedListener listener) {
        try {
            context.unregisterReceiver(listener);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
